package com.zyl.file.util;

import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.zyl.file.exception.MyException;
import com.zyl.file.result.ResultCode;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

/**
 * @author liugang
 * @Desc
 * @date 14:23 2021/11/16
 */
@Slf4j
public class FileUtils {

    public static final String FILE_SEPARATOR = System.getProperty("file.separator");

    /**
     * 文件下载
     *
     * @param response 请求回执
     * @param filePath 文件地址
     */
    public static void fileWriter(HttpServletResponse response, String filePath) {
        File file = new File(filePath);
        if (file.exists()) {
            FileInputStream inputStream = null;
            try {
                inputStream = new FileInputStream(file);
            } catch (FileNotFoundException e) {
                // todo 异常处理不规范
                log.error("文件异常：", e);
            }
            response.setContentType("application/ostet-stream");
            response.setHeader("Content-disposition", "attachment;filename=" + file.getName());
            OutputStream outputStream = null;
            try {
                outputStream = response.getOutputStream();
                byte[] bytes = new byte[1024];
                while (inputStream.read(bytes) != -1) {
                    outputStream.write(bytes);
                }
                outputStream.flush();
            } catch (IOException e) {
                // todo 异常处理不规范
                log.error("意外错误：", e);
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    if (outputStream != null) {
                        outputStream.close();
                    }
                } catch (IOException e) {
                    // todo 异常处理不规范
                    log.error("意外错误：", e);
                }
            }
        }
    }

    /**
     * 文件下载
     *
     * @param response 请求回执
     */
    public static void fileWriter(HttpServletResponse response, OutputStream outputStream, String fileName) {
        if (outputStream != null) {
            response.setContentType("application/ostet-stream");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName);
            try {
                outputStream.flush();
            } catch (IOException e) {
                log.error("意外错误：", e);
            } finally {
                try {
                    if (outputStream != null) {
                        outputStream.close();
                    }
                } catch (IOException e) {
                    log.error("意外错误：", e);
                }
            }
        }
    }

    /**
     * 返回附件
     *
     * @param response 响应
     * @param filename 文件名
     * @param content  附件内容
     * @throws IOException
     */
    public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {
        // 设置 header 和 contentType
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        // 输出附件
        IoUtil.write(response.getOutputStream(), false, content);
    }

    /**
     * 文件打印
     *
     * @param response 请求回执
     */
    public static void printFile(HttpServletResponse response, OutputStream outputStream, String fileName) {
        if (outputStream != null) {
//            String suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
//            String contentType = getContentTypeByExtension(suffix);
//            response.setContentType(contentType);
            response.setHeader("Content-disposition", "inline;filename=" + fileName);
            try {
                outputStream.flush();
            } catch (IOException e) {
                log.error("意外错误：", e);
            } finally {
                try {
                    if (outputStream != null) {
                        outputStream.close();
                    }
                } catch (IOException e) {
                    log.error("意外错误：", e);
                }
            }
        }
    }

    /**
     * 判断ContentType
     *
     * @param fileExtension
     * @return
     */
    public static String getContentTypeByExtension(String fileExtension) {
        if (fileExtension == null) {
            return null;
        }

        String contentType;
        switch (fileExtension.toLowerCase()) {
            case "pdf":
                contentType = "application/pdf";
                break;
            case "txt":
                contentType = "text/plain";
                break;
            case "jpg":
            case "jpeg":
                contentType = "image/jpeg";
                break;
            case "png":
                contentType = "image/png";
                break;
            case "doc":
            case "docx":
                contentType = "application/vnd.ms-word";
                break;
            case "xls":
            case "xlsx":
                contentType = "application/vnd.ms-excel";
                break;
            // 添加更多文件扩展名和对应的 MIME 类型
            default:
                contentType = "application/octet-stream";
                break;
        }

        return contentType;
    }

    /**
     * url转file
     */
    public static File getFileByUrl(String url, String tempPath) {
        url = url.replaceAll("\\\\", "//");
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        BufferedOutputStream stream = null;
        InputStream inputStream = null;
        File file = null;
        try {
            URL fileUrl = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) fileUrl.openConnection();
            conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
            inputStream = conn.getInputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = inputStream.read(buffer)) != -1) {
                outStream.write(buffer, 0, len);
            }

            file = new File(tempPath);
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }

            FileOutputStream fileOutputStream = new FileOutputStream(file);
            stream = new BufferedOutputStream(fileOutputStream);
            stream.write(outStream.toByteArray());
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (stream != null) {
                    stream.close();
                }
                outStream.close();
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
        return file;
    }

    /**
     * 文件磁盘路径转file
     */
    public static File getFileByPath(String url, String tempPath) {
        url = url.replaceAll("\\\\", "//");
        // 对本地文件命名，path是http的完整路径，主要得到资源的名字
        String newUrl = url;
        newUrl = newUrl.split("[?]")[0];
        String[] bb = newUrl.split("/");
        // 得到最后一个分隔符后的名字
        String fileName = bb[bb.length - 1];
        // 保存到本地的路径
        String filePath = tempPath + fileName;
        File file = null;

        URL urlfile;
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            // 判断文件的父级目录是否存在，不存在则创建
            file = new File(filePath);
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            // 创建文件
            file.createNewFile();
            // 下载
            urlfile = new URL(newUrl);
            inputStream = urlfile.openStream();
            outputStream = new FileOutputStream(file);

            int bytesRead = 0;
            byte[] buffer = new byte[8192];
            while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            try {
                if (null != outputStream) {
                    outputStream.close();
                }
                if (null != inputStream) {
                    inputStream.close();
                }

            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
        return file;
    }

    /**
     * 创建文件
     *
     * @param file
     */
    public static void creatFile(File file) {
        creatFile(file.getAbsolutePath());
    }

    /**
     * 创建文件
     *
     * @param name 文件名
     */
    public static void creatFile(String name) {
        try {
            File file = new File(name);
            if (!file.exists()) {
                sureParentFile(file);
                file.createNewFile();
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 确保上级文件夹存在
     *
     * @param file 文件夹路径
     */
    private static void sureParentFile(File file) {
        File par = file.getParentFile();
        if (!par.exists()) {
            sureParentFile(par);
            par.mkdir();
        }
    }

    /**
     * 文件转base64
     *
     * @param file
     * @return
     * @throws IOException
     */
    public static byte[] getContent(File file) {

        long fileSize = file.length();
        if (fileSize > Integer.MAX_VALUE) {
            log.error("file too big...");
            return null;
        }
        byte[] buffer = new byte[(int) fileSize];
        FileInputStream fi = null;
        try {
            // 确保所有数据均被读取
            fi = new FileInputStream(file);
            int offset = 0;
            int numRead = 0;
            while (offset < buffer.length && (numRead = fi.read(buffer, offset, buffer.length - offset)) >= 0) {
                offset += numRead;
            }
            if (offset != buffer.length) {
                log.error("Could not completely read file " + file.getName());
                return null;
            }
            return buffer;
        } catch (IOException e) {
            log.error("Could not completely read file{}", file.getName(), e);
            return null;
        } finally {
            IoUtils.close(fi);
        }

    }

    /**
     * 拷贝文件
     *
     * @param srcFileName  待复制文件名
     * @param destFileName 目标文件名
     * @param overlay      如果目标文件存在，是否覆盖
     * @return
     * @throws IOException
     */
    public static boolean copyFile(String srcFileName, String destFileName, boolean overlay) throws IOException {
        File srcFile = new File(srcFileName);
        // 判断源文件是否存在
        if (!srcFile.exists()) {
            log.debug("源文件：{}不存在！", srcFileName);
            return false;
        } else if (!srcFile.isFile()) {
            log.debug("复制文件失败，源文件：{}不是一个文件！", srcFileName);
            return false;
        }
        File destFile = new File(destFileName);
        // 判断目标文件是否存在
        if (destFile.exists()) {
            if (overlay) {
                new File(destFileName).delete();
            }
        } else {
            // 如果目标文件所在目录不存在，则创建目录
            if (!destFile.getParentFile().exists()) {
                if (!destFile.getParentFile().exists()) {
                    // 复制文件失败;创建目标文件所在目录失败
                    return false;
                }
            }
        }
        // 复制文件
        // 读取的字节数
        int byteRead = 0;
        InputStream in = null;
        OutputStream out = null;
        try {
            in = new FileInputStream(srcFile);
            out = new FileOutputStream(destFile);
            byte[] buffer = new byte[1024];
            while ((byteRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, byteRead);
            }
            return true;
        } catch (FileNotFoundException e) {
            throw e;
        } catch (IOException e) {
            throw e;
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    /**
     * 删除文件夹
     *
     * @param folder 文件夹
     * @throws Exception
     */
    public static void deleteFolder(File folder) throws Exception {
        if (!folder.exists()) {
            throw new Exception("文件不存在");
        }
        File[] files = folder.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    // 递归直到目录下没有文件
                    deleteFolder(file);
                } else {
                    // 删除
                    file.delete();
                }
            }
        }
        // 删除
        folder.delete();
    }

    /**
     * 根据输入流、生成文件
     *
     * @param inputStream
     * @param newFile
     */
    public static void cpFile(InputStream inputStream, File newFile) {
        if (newFile == null) {
            return;
        }
        if (!newFile.exists()) {
            FileOutputStream outputStream = null;
            try {
                newFile.createNewFile();
                outputStream = new FileOutputStream(newFile);
                byte[] bytes = new byte[1024];
                for (int k = 0; (k = inputStream.read(bytes)) != -1; ) {
                    outputStream.write(bytes, 0, k);
                    outputStream.flush();
                }
            } catch (IOException e) {
            } finally {
                try {
                    if (outputStream != null) {
                        outputStream.close();
                    }
                } catch (IOException e) {
                }
                try {
                    inputStream.close();
                } catch (IOException e) {
                }
            }
        }

    }

    /**
     * 删除文件，可以是文件或文件夹
     *
     * @param fileName 要删除的文件名
     * @return 删除成功返回true，否则返回false
     */
    public static boolean delete(String fileName) {
        File file = new File(fileName);
        if (!file.exists()) {
            return false;
        } else {
            if (file.isFile()) {
                return deleteFile(fileName);
            } else {
                return deleteDirectory(fileName);
            }
        }
    }

    /**
     * 删除单个文件
     *
     * @param fileName 要删除的文件的文件名
     * @return 单个文件删除成功返回true，否则返回false
     */
    public static boolean deleteFile(String fileName) {
        File file = new File(fileName);
        // 如果文件路径所对应的文件存在，并且是一个文件，则直接删除
        if (file.exists() && file.isFile()) {
            return file.delete();
        } else {
            return false;
        }
    }

    /**
     * 删除目录及目录下的文件
     *
     * @param dir 要删除的目录的文件路径
     * @return 目录删除成功返回true，否则返回false
     */
    public static boolean deleteDirectory(String dir) {
        // 如果dir不以文件分隔符结尾，自动添加文件分隔符
        if (!dir.endsWith(File.separator)) {
            dir = dir + File.separator;
        }
        File dirFile = new File(dir);
        // 如果dir对应的文件不存在，或者不是一个目录，则退出
        if ((!dirFile.exists()) || (!dirFile.isDirectory())) {
            return false;
        }
        boolean flag = true;
        // 删除文件夹中的所有文件包括子目录
        File[] files = dirFile.listFiles();
        for (int i = 0; i < Objects.requireNonNull(files).length; i++) {
            // 删除子文件
            if (files[i].isFile()) {
                flag = FileUtils.deleteFile(files[i].getAbsolutePath());
                if (!flag) {
                    break;
                }
            } else if (files[i].isDirectory()) {
                // 删除子目录
                flag = FileUtils.deleteDirectory(files[i].getAbsolutePath());
                if (!flag) {
                    break;
                }
            }
        }
        if (!flag) {
            return false;
        }
        // 删除当前目录
        return dirFile.delete();
    }

    /**
     * 根据url下载文件流
     *
     * @param urlStr
     * @return
     */
    public static InputStream getInputStreamFromUrl(String urlStr) {
        InputStream inputStream = null;
        try {
            // url解码
            URL url = new URL(java.net.URLDecoder.decode(urlStr, "UTF-8"));
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 设置超时间为3秒
            conn.setConnectTimeout(3 * 1000);
            // 防止屏蔽程序抓取而返回403错误
            conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
            // 得到输入流
            inputStream = conn.getInputStream();
        } catch (IOException e) {

        }
        return inputStream;
    }

    /**
     * @param inputStream
     * @return
     * @throws IOException
     */
    public static byte[] read(InputStream inputStream) throws IOException {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int num = inputStream.read(buffer);
            while (num != -1) {
                baos.write(buffer, 0, num);
                num = inputStream.read(buffer);
            }
            baos.flush();
            return baos.toByteArray();
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

    /**
     * InputStream转ByteArrayOutputStream
     *
     * @param inputStreamFromUrl
     * @return
     */
    public static ByteArrayOutputStream getByteOutputStream(InputStream inputStreamFromUrl) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int nRead;
        byte[] data = new byte[1024];
        try {
            while ((nRead = inputStreamFromUrl.read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, nRead);
            }
            buffer.flush();
            return buffer;
        } finally {
            if (inputStreamFromUrl != null) {
                inputStreamFromUrl.close();
            }
        }
    }

    /**
     * 统一转换
     *
     * @param request
     * @param inputStreams
     * @param fileNames
     * @throws IOException
     */
    public static void transhandleFile(MultipartHttpServletRequest request, List<byte[]> inputStreams,
                                       List<String> fileNames) {
        try {
            MultipartHttpServletRequest multipartRequest = request;
            Iterator<String> itr = multipartRequest.getFileNames();
            List<MultipartFile> files = new ArrayList<>();
            while (itr.hasNext()) {
                List<MultipartFile> files1 = multipartRequest.getFiles(itr.next());
                files.addAll(files1);
            }
            if (CollectionUtils.isEmpty(files)) {
                throw new MyException(ResultCode.PARAM_ERROR, "请选择上传文件");
            }
            for (MultipartFile file : files) {

                byte[] read = FileUtils.read(file.getInputStream());
                inputStreams.add(read);
                fileNames.add(file.getOriginalFilename());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 统一转换
     *
     * @param request
     * @throws IOException
     */
    public static List<MultipartFile> transhandleFile(MultipartHttpServletRequest request) {
        try {
            MultipartHttpServletRequest multipartRequest = request;
            Iterator<String> itr = multipartRequest.getFileNames();
            List<MultipartFile> files = new ArrayList<>();
            while (itr.hasNext()) {
                List<MultipartFile> files1 = multipartRequest.getFiles(itr.next());
                files.addAll(files1);
            }
            return files;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new ArrayList<>();
    }

    /**
     * 统一转换(不传文件时不报错)
     *
     * @param request
     * @param inputStreams
     * @param fileNames
     * @throws IOException
     */
    public static void transhandleFile1(MultipartHttpServletRequest request, List<byte[]> inputStreams,
                                        List<String> fileNames) {
        try {
            MultipartHttpServletRequest multipartRequest = request;
            Iterator<String> itr = multipartRequest.getFileNames();
            List<MultipartFile> files = new ArrayList<>();
            while (itr.hasNext()) {
                List<MultipartFile> files1 = multipartRequest.getFiles(itr.next());
                files.addAll(files1);
            }
            if (CollectionUtils.isEmpty(files)) {
                return;
            }
            for (MultipartFile file : files) {

                byte[] read = FileUtils.read(file.getInputStream());
                inputStreams.add(read);
                fileNames.add(file.getOriginalFilename());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建临时文件
     * 该文件会在 JVM 退出时，进行删除
     *
     * @param data 文件内容
     * @return 文件
     */
    @SneakyThrows
    public static File createTempFile(String data) {
        File file = createTempFile();
        // 写入内容
        FileUtil.writeUtf8String(data, file);
        return file;
    }

    /**
     * 创建临时文件
     * 该文件会在 JVM 退出时，进行删除
     *
     * @param data 文件内容
     * @return 文件
     */
    @SneakyThrows
    public static File createTempFile(byte[] data) {
        File file = createTempFile();
        // 写入内容
        FileUtil.writeBytes(data, file);
        return file;
    }

    /**
     * 创建临时文件，无内容
     * 该文件会在 JVM 退出时，进行删除
     *
     * @return 文件
     */
    @SneakyThrows
    public static File createTempFile() {
        // 创建文件，通过 UUID 保证唯一
        File file = File.createTempFile(IdUtil.simpleUUID(), null);
        // 标记 JVM 退出时，自动删除
        file.deleteOnExit();
        return file;
    }

    /**
     * 生成文件路径
     *
     * @param content      文件内容
     * @param originalName 原始文件名
     * @return path，唯一不可重复
     */
    public static String generatePath(byte[] content, String originalName) {
        String sha256Hex = DigestUtil.sha256Hex(content);
        // 情况一：如果存在 name，则优先使用 name 的后缀
        if (StrUtil.isNotBlank(originalName)) {
            String extName = FileNameUtil.extName(originalName);
            return StrUtil.isBlank(extName) ? sha256Hex : sha256Hex + "." + extName;
        }
        // 情况二：基于 content 计算
        return sha256Hex + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content));
    }
}
